<!--
 https://github.com/stevenrskelton/sortable-table
-->

<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="json-5.html">

<polymer-element
	name="sortable-table"
	attributes="
		data columns sortColumn sortDescending rowSelection multiSelect selected
		headerTemplate cellTemplate rowTemplate footerTemplate checkbox pageSize page
		rowEditorTemplate disableColumnMove defaultStyle datalen curPage"
	style="display:block"
>
<template>
	<style>
	.sortable-table {
		width: 100%;
		max-width: 100%;
		border-collapse: collapse;
	}
	.checkbox-column {
		width: 1em;
		vertical-align: middle;
	}
	.checkbox-column input[type=checkbox] {
		display: block;
		margin-left: auto;
		margin-right: auto;
	}
	th.checkbox-column input[type=checkbox] {
		vertical-align: middle;
	}
	</style>
	<template if="{{defaultStyle}}">
		<style>
			.sortable-table {
				border-collapse: collapse;
				border: 1px solid #ccc;
			}
			.sortable-table .row-selection {
				cursor: pointer;
			}
			.sortable-table tbody td {
				border: 1px dotted #ccc;
			}
			.sortable-table tbody tr:nth-of-type(even) {
				background-color: rgba(255,255,224,0.25);
			}
			.sortable-table tfoot td {
				border: 1px solid #ccc;
				background-color: #F5F8FA;
			}
			.sortable-table thead th {
				border: 1px solid #ccc;
				background-color: #0084B4;
				color: white;
				cursor: pointer;
			}
			.sortable-table thead th.sorted-column-asc {
				text-decoration: overline;
			}
			.sortable-table thead th.sorted-column-desc {
				text-decoration: underline;
			}
			.sortable-table thead th.column-move-right {
				border-right-color: #FBBA37;
				background: #FBBA37; /* Older browsers */
				background: -moz-linear-gradient(left, #0084B4 50%, #FBBA37 100%); /* FF3.6+ */
				background: -webkit-linear-gradient(left, #0084B4 50%, #FBBA37 100%); /* Chrome10+,Safari5.1+ */
				background: -o-linear-gradient(left, #0084B4 50%, #FBBA37 100%); /* Opera 11.10+ */
				background: -ms-linear-gradient(left, #0084B4 50%, #FBBA37 100%); /* IE10+ */
				background: linear-gradient(left, #0084B4 50%, #FBBA37 100%); /* W3C */
			}
			.sortable-table thead th.column-move-left {
				border-left-color: #FBBA37;
				background: #FBBA37; /* Older browsers */
				background: -moz-linear-gradient(left, #FBBA37 0%, #0084B4 50%); /* FF3.6+ */
				background: -webkit-linear-gradient(left, #FBBA37 0%, #0084B4 50%); /* Chrome10+,Safari5.1+ */
				background: -o-linear-gradient(left, #FBBA37 0%, #0084B4 50%); /* Opera 11.10+ */
				background: -ms-linear-gradient(left, #FBBA37 0%, #0084B4 50%); /* IE10+ */
				background: linear-gradient(left, #FBBA37 0%, #0084B4 50%); /* W3C */
			}
			.sortable-table thead th.column-in-move {
				opacity: 0.4;
			}
			.sortable-table tbody tr.selected-row {
				background-color: rgba(0,96,200,0.2);
			}
		</style>
	</template>

	<template id="defaultPaging">
		<div style="text-align:center">
			<div style="float:left;cursor:pointer;color:{{page == 1 ? '#AAA':'#000'}}" on-click="{{moveToPreviousPage}}">Prev</div>
			<div style="float:right;cursor:pointer;color:{{page * pageSize > data.length ? '#AAA':'#000'}}" on-click="{{moveToNextPage}}">Next</div>
			Page {{page}} of {{lastPage}}
		</div>
	</template>

    <template id="myFooterPaging">
        <div style="text-align:center">
            <span style="cursor:pointer;display:{{curPage == 1 ? 'none':'inline'}}"
                  on-click="{{ moveToFirstPage }}">First
            </span>
            <span style="margin:0 8px;cursor:pointer;display:{{curPage == 1 ? 'none':'inline'}}"
                  on-click="{{ moveToPreviousPage }}">Prev
            </span>
            <span style="">Page <input type="number"
                                       style="width:30px;text-align:right;margin:0" min="1"
                                       max="{{lastPage}}" value="{{curPage}}" on-change="{{moveToOnePage}}"> of {{lastPage}}</span>
            <span style="margin:0 8px;cursor:pointer;display:{{curPage * pageSize >= datalen ? 'none':'inline'}}"
                  on-click="{{moveToNextPage}}">Next
            </span>
            <span style="cursor:pointer;display:{{curPage * pageSize >= datalen ? 'none':'inline'}}"
                  on-click="{{ moveToLastPage }}">Last
            </span>
            <!--<span style="float: right">Page Size <input type="number"-->
                                              <!--style="width:30px;text-align:right;margin:0" min="1"-->
                                              <!--value="{{pageSize}}"></span>-->
        </div>
    </template>

	<template id="bootstrapPaging">
		<div style="width:100%;text-align:center;">
			<ul class="pagination">
				<li class="{{page==1 ? 'disabled' : ''}}" on-click="{{moveToFirstPage}}"><a href="#" onclick="return false;">«</a></li>
				<template repeat="{{i in lastPage | naturalNumbers(page)}}">
					<li class="{{page==i ? 'active' : ''}}" on-click="{{moveToPage}}"><a href="#" onclick="return false;">{{i}}</a></li>
				</template>
				<li class="{{page==lastPage ? 'disabled' : ''}}" on-click="{{moveToLastPage}}"><a href="#" onclick="return false;">»</a></li>
			</ul>
		</div>
	</template>

	<template id="extjsPaging">
		<div horizontal layout center>
			<button disabled?="{{page==1}}" on-click="{{moveToFirstPage}}">&#9198;</button>
			<button disabled?="{{page<=1}}" on-click="{{moveToPreviousPage}}">&#9194;</button>

			<span class="x-toolbar-separator-horizontal"></span>

			<span style="margin:0 8px">Page <input type="number" style="width:30px;text-align:right;margin:0" min="1" max="{{lastPage}}" value="{{page}}"> of {{lastPage}}</span>

			<span class="x-toolbar-separator-horizontal"></span>

			<button disabled?="{{page>=lastPage}}" on-click="{{moveToNextPage}}">&#9193;</button>
			<button disabled?="{{page==lastPage}}" on-click="{{moveToLastPage}}">&#9197;</button>
			<span flex style="text-align:right;margin-right:6px;">Displaying {{page > lastPage ? (lastPage-1) * pageSize + 1 : ((page - 1) * pageSize) + 1}} - {{page >= lastPage ? data.length : page * pageSize}} of {{data.length}}</span>
		</div>
	</template>

	<template id="espnPaging">
		<div horizontal layout center>
			<span flex>{{data.length}} Results</span>
			<span>
				<button disabled?="{{page<=1}}" on-click="{{moveToPreviousPage}}">&#10094;</button>
				{{page}} of {{lastPage}}
				<button disabled?="{{page>=lastPage}}" on-click="{{moveToNextPage}}">&#10095;</button>
			</span>
		</div>
	</template>

	<table class="sortable-table">
		<thead>
			<tr on-click="{{changeSort}}">
				<template if="{{checkbox}}">
					<th class="checkbox-column"><input type="checkbox" on-change="{{checkall}}" hidden?="{{!multiSelect}}" checked?="{{data.length == selected.length}}"></th>
				</template>
				<template repeat="{{column in columns}}">
					<template ref="{{column.headerTemplate || headerTemplate}}" bind>
						<th
							class="column-{{column.name}}
								{{sortColumn == column.name && sortDescending ? 'sorted-column-desc' : ''}}
								{{sortColumn == column.name && !sortDescending ? 'sorted-column-asc' : ''}}"
							draggable="{{!disableColumnMove && !(rowTemplate || rowEditorTemplate)}}"
							on-dragstart="{{handleTHDragStart}}"
							on-dragenter="{{handleTHDragEnter}}"
							on-dragover="{{handleTHDragOver}}"
							on-dragleave="{{handleTHDragLeave}}"
							on-drop="{{handleTHDrop}}"
							on-dragend="{{handleTHDragEnd}}"
						>{{!(column.title) ? column.name : column.title}}</th>
					</template>
				</template>
			</tr>
		</thead>
		<tbody>
			<template repeat="{{record in data | sortByKey(sortColumn, sortDescending, columns, selected, pageSize, page, editRow, (selected || []).length + data.length, forceRefresh)}}">
				<tr
					class="{{record.selected ? 'selected-row' : '' }}"
					on-click="{{rowSelect}}"
					on-dblclick="{{rowEdit}}"
				>
					<template if="{{checkbox}}">
						<td class="checkbox-column"><input type="checkbox" on-change="{{checkedbox}}" checked?="{{record.selected}}"></td>
					</template>
					<template if="{{record.edit}}" ref="{{rowEditorTemplate}}"></template>
					<template if="{{!record.edit}}" ref="{{rowTemplate}}" bind>
						<template repeat="{{column in columns}}" bind>
							<template ref="{{column.cellTemplate || cellTemplate}}" bind="{{record.fields[column.name]}}">
								<td
									class="column-{{column.name}}
										{{sortColumn == column.name && sortDescending ? 'sorted-column-desc' : ''}}
										{{sortColumn == column.name && !sortDescending ? 'sorted-column-asc' : ''}}"
								>{{value}}</td>
							</template>
						</template>
					</template>
				</tr>
			</template>
		</tbody>
		<tfoot>
			<template if="{{showFooter && forceFooterRefresh}}">
				<tr>
					<template if="{{checkbox}}">
						<td></td>
					</template>
					<template repeat="{{column in columns}}">
						<template bind="{{templateData in data | selectProperty(column, forceFooterRefresh)}}">
							<template ref="{{column.footerTemplate}}" bind="{{templateData}}">
								<td></td>
							</template>
						</template>
					</template>
				</tr>
			</template>
			<template if="{{footerTemplate && forceRefresh && forceFooterRefresh}}">
				<tr>
					<td colspan="{{columns.length + (checkbox ? 1 : 0)}}"><template ref="{{footerTemplate}}" bind></template></td>
				</tr>
			</template>
		</tfoot>
	</table>
</template>

<script>
	"use strict";
	Polymer('sortable-table', {
		editRow: null,
		disableColumnMove: false,
		footerTemplate: null,
		headerTemplate: null,
		lastPage: 1,
		multiSelect: false,
		defaultStyle: false,
		page: 1,
		pageSize: Number.MAX_VALUE,
		rowEditorTemplate: null,
		rowSelection: false,
		rowTemplate: null,
		selected: null,
		sortColumn: null,
		sortDescending: false,
		showFooter: false,
        datalen:0,
        curPage:1,
		created: function(){
			if(!this.data) this.data = [];
			if(!this.columns) this.columns = [];
		},
		ready: function(){
			var array = [];
			var self = this;
			[].forEach.call(this.childNodes, function(element) {
				if(element.nodeName === "#text"){
					var text = element.textContent.trim();
					if(text.length>0){
						var data;
						try{
							data = JSON.parse(text);
						} catch(e){
							try {
								data = JSON5.parse(text);
							} catch(e){
								alert('Could not parse data.\n\nArrays, JSON and JSON5 are supported.');
							}
						}
						if(data){
							self.data = data;
							self.rebuildColumns();
						}else self.data = [];
					}
				}
			});
			this.onMutation(this, this.ready);
		},
		/**
		 *	Logic
		 */
		isArray: function(a){
			return Object.prototype.toString.call(a) === '[object Array]';
		},
		toggleRowFromSelected: function(row){
			if(this.isArray(this.selected)){
				var index = this.selected.indexOf(row);
				if(index > -1){
					this.selected.splice(index, 1);
				}else{
					this.selected.push(row);
				}
			}else{
				if(this.selected === row) this.selected = null;
				else this.selected = row;
			}
		},
		columnWatches: [],
		addTemplateIfNotInDocument: function(templateId){
			/* copy template from content into shadow dom */
			if(templateId && this.shadowRoot && !this.shadowRoot.getElementById(templateId)){
				var t = this.querySelector('#'+templateId);
				if(t) this.shadowRoot.appendChild(t);
				else alert('Could not find template `' + templateId + '`');
			}
		},
		rebuildColumns: function(){
			if(this.columns.length === 0 && this.data && this.data.length > 0){
				//try and load from DOM
				var columnNodes = this.querySelectorAll('sortable-column');
				var c = [];
				var self = this;
				if(columnNodes.length > 0){
					[].forEach.call(columnNodes, function(n, index) {
						var name = n.getAttribute('name');
						if(!name || name === '') name = index;
						if(String(name).indexOf(' ') > -1) alert('Column name `'+name+'` contains an illegal character (` `)');
						var title = n.getAttribute('title');
						if(!title || title === '') title = n.textContent.trim();
						if(title === '') title = name;
						var formula = n.getAttribute('formula');
						if(formula){
							var match = formula.match(/function[^\(]*\(([^\)]*)\)[^\{]*{([^\}]*)\}/);
							if (match) {
								var args = match[1].split(',').map(function(arg) { return arg.replace(/\s+/, ''); });
								formula = new Function(args, match[2]);
							}else if(self[formula]){
								//not an inlined function, assume it's a member of this
								formula = self[formula]
							}else if(PolymerExpressions.prototype[formula]){
								//not an inlined function, or member of this, check PolymerExpressions.prototype
								formula = PolymerExpressions.prototype[formula];
							}else alert('Could not load formula `'+ formula +'` for column `' + name + '`');
						}
						c.push({
							name: name, title: title, formula: formula,
							cellTemplate: n.getAttribute('cellTemplate'),
							headerTemplate: n.getAttribute('headerTemplate'),
							footerTemplate: n.getAttribute('footerTemplate')
						});
					});
					this.columns = c;
					this.columnsChanged();
				}else{
					var unique = [];
					for(var i=0;i<this.data.length;i++){
						Object.keys(this.data[i]).forEach(function(property) {
							if (unique.indexOf(property) === -1) {
								unique.push(property);
								c.push({name: property});
							}
						});
					}
					this.columns = c;
				}
			}
		},
		/**
		 *	Change Observers
		 */
		columnsChanged: function(){
			var self = this;
			//explode columns if simple text array
			if(self.columns.length > 0 && typeof self.columns[0] === 'string'){
				self.columns = self.columns.map(function(column, index, array) {
					return { name: index, title: column };
				});
			}
			//watch templateIds since if added/modified we need to copy those templates into the shadow dom
			function addTemplateWatches(column, field){
				for(var i=0;i<self.columnWatches.length;i++){
					if(self.columnWatches[i].object_ === column && self.columnWatches[i].path_[0] === field) return;
				}
				var observer = new PathObserver(c, field);
				self.columnWatches.push(observer);
				observer.open(function(added, removed, changed, getOldValueFn) {
					self.columnsChanged();
				});
			}
			for(var i=0;i<this.columns.length;i++){
				var c = this.columns[i];
				this.addTemplateIfNotInDocument(c.cellTemplate);
				this.addTemplateIfNotInDocument(c.headerTemplate);
				this.addTemplateIfNotInDocument(c.footerTemplate);
				if(c.footerTemplate) this.showFooter = true;
				addTemplateWatches(c,'cellTemplate');
				addTemplateWatches(c,'headerTemplate');
				addTemplateWatches(c,'footerTemplate');
			}
			this.forceRefresh++;
		},
		rowSelectionChanged: function(a,val){
			var table = this.shadowRoot.querySelector('tbody')
			if(table){
				if(val) table.classList.add('row-selection');
				else table.classList.remove('row-selection');
			}
		},
		multiSelectChanged: function(a,val){
			if(val){
				if(!this.isArray(this.selected)){
					if(this.selected) this.selected = [this.selected];
					else this.selected = [];
				}
			}else if(this.isArray(this.selected)){
				if(this.selected.length > 0) this.selected = this.selected[0];
				else this.selected = null;
			}
		},
		selectedChanged: function(a,val){
			if(val){
				if(this.isArray(val)){
					if(!this.multiSelect) this.multiSelect = true;
				}else{
					if(this.multiSelect) this.multiSelect = false;
				}
			}
		},
		dataChanged: function(){ this.rebuildColumns(); this.pageSizeChanged(); this.datalen = Math.max(this.datalen,this.data.length);},
		cellTemplateChanged: function(a, t){ this.addTemplateIfNotInDocument(t); },
		rowTemplateChanged: function(a, t){ this.addTemplateIfNotInDocument(t); },
		headerTemplateChanged: function(a, t){ this.addTemplateIfNotInDocument(t); this.forceRefresh++; },
		footerTemplateChanged: function(a, t){ this.addTemplateIfNotInDocument(t); this.forceRefresh++; },
		rowEditorTemplateChanged: function(a, t){ this.addTemplateIfNotInDocument(t); },
		pageSizeChanged: function(){
            this.lastPage = Math.max(1,Math.ceil(this.datalen / this.pageSize));
        },
		/**
		 *	Row Edit
		 */
		rowEdit: function(e,p){
			if(this.rowEditorTemplate){
				var model = e.target.templateInstance.model;
				var row = model.row;
				if(model.record) row = model.record.row;
				if(this.editRow !== row){
					this.originalEditRow = JSON.parse(JSON.stringify(row));
					this.editRow = row;
				}
			}
		},
		closeEdit: function(e){
			this.originalEditRow = null;
			this.editRow = null;
		},
		undoEdit: function(e){
			if(this.originalEditRow && this.editRow){
				var self = this;
				Object.getOwnPropertyNames(this.originalEditRow).forEach(function(val, idx, array) {
					if(self.editRow[val] !== self.originalEditRow[val] && self.originalEditRow[val])
						self.editRow[val] = self.originalEditRow[val];
				});
				this.refreshFooter();
			}
		},
		cancelEdit: function(e){
			this.undoEdit.call(this, e); this.closeEdit.call(this, e);
		},
		/**
		 *	Checkbox helper functions
		 */
		checkbox: false,
		checkedbox: function(e,p){
			var row = e.target.templateInstance.model.record.row;
			this.toggleRowFromSelected(row);
			e.preventDefault();
		},
		checkall: function(e,p){
			if(e.target.checked){
				for(var i=0;i<this.data.length;i++){
					if(this.selected.indexOf(this.data[i]) === -1) this.selected.push(this.data[i]);
				}
			}else{
				this.selected = [];
			}
		},
		/**
		 *	Row Editor helper functions
		 */
        moveToOnePage: function(){ this.fire('request-page',this.curPage); },
		moveToPage: function(e){ this.curPage = Number(e.currentTarget.textContent); this.fire('request-page',this.curPage);},
		moveToPreviousPage: function(){ if(this.curPage > 1) this.curPage--; this.fire('request-page',this.curPage);},
		moveToNextPage: function(){ if(this.datalen > this.curPage * this.pageSize) this.curPage++; this.fire('request-page',this.curPage);},
		moveToFirstPage: function(){ this.curPage = 1; this.fire('request-page',this.curPage);},
		moveToLastPage: function(){ this.curPage = this.lastPage; this.fire('request-page',this.curPage);},
        delResponse: function(){ this.fire('del-response'); },
		/**
		 *	Column Drag-and-Drop
		 */
		dragColumn: null,
		handleTHDragStart: function(e) {
			if(!this.disableColumnMove && !(this.rowTemplate || this.rowEditorTemplate)){
				this.dragColumn = {
					th: e.target,
					column: e.target.templateInstance.model.column,
					entered: null,
					index: this.columns.indexOf(e.target.templateInstance.model.column)
				};
				e.target.classList.add('column-in-move');
				e.dataTransfer.effectAllowed = 'move';
				e.dataTransfer.setData('application/sortable-table-column-name', this.dragColumn.index);
			}
		},
		handleTHDragOver: function(e) {
			if (e.preventDefault) e.preventDefault(); // Necessary. Allows us to drop.
			e.dataTransfer.dropEffect = 'move';
			this.handleTHDragEnter(e);
			return false;
		},
		findDropSide: function(e,self){
			var th = e.target;
			var index = self.columns.indexOf(th.templateInstance.model.column);
			if((index < self.dragColumn.index - 1) || (index > self.dragColumn.index + 1)){
				if(th.getBoundingClientRect){
					var center = th.clientWidth/2 + th.getBoundingClientRect().left;
					return (center >= e.clientX) ? -1 : 1;
				}else return 0;
			}else if(index === self.dragColumn.index - 1){
				return -1;
			}else if(index === self.dragColumn.index + 1){
				return 1;
			}
		},
		handleTHDragEnter: function(e) {
			if(this.dragColumn){
				var th = e.target;
				this.dragColumn.entered = null;
				while(th.tagName !== 'TH' && th.parentElement){
					th = th.parentElement;
					if(th.tagName === 'TH') this.dragColumn.entered = th;
				}
				var dragSide = this.findDropSide(e,this);
				if(dragSide < 0){
					th.classList.remove('column-move-right');
					th.classList.add('column-move-left');
				}else if(dragSide > 0){
					th.classList.remove('column-move-left');
					th.classList.add('column-move-right');
				}
				[].forEach.call(this.shadowRoot.querySelectorAll('th'), function(element) {
					if(element!==th){
						element.classList.remove('column-move-left');
						element.classList.remove('column-move-right');
					}
				});
			}
		},
		handleTHDragLeave: function(e) {
			if(this.dragColumn){
				var th = e.target;
				if(this.dragColumn.entered === th) this.dragColumn.entered === null;
				else if(th.classList){
					if(th.classList.contains('column-move-left')) th.classList.remove('column-move-left');
					if(th.classList.contains('column-move-right')) th.classList.remove('column-move-right');
				}
			}
		},
		handleTHDrop: function(e) {
			if (e.stopPropagation) e.stopPropagation(); // stops the browser from redirecting.
			if(this.dragColumn && this.dragColumn.th.parentNode === e.target.parentNode){
				var newIndex = this.columns.indexOf(e.target.templateInstance.model.column);
				var oldIndex = +e.dataTransfer.getData('application/sortable-table-column-name');
				var dragSide = this.findDropSide(e,this);
				if(newIndex < oldIndex && dragSide === 1) newIndex++;
				else if(newIndex > oldIndex && dragSide === -1) newIndex--;
				if(newIndex !== oldIndex){
					var b = this.columns.splice(oldIndex,1);
					this.columns.splice(newIndex,0,b[0]);
				}
			}
			return false;
		},
		handleTHDragEnd: function(e) {
			if(this.dragColumn){
				this.dragColumn.th.classList.remove('column-in-move');
				[].forEach.call(this.shadowRoot.querySelectorAll('th'), function(element) {
					element.classList.remove('column-move-left');
					element.classList.remove('column-move-right');
				});
			}
			this.dragColumn = null;
		},
		/**
		 *	Template Functions
		 */
		changeSort: function(e,p){
			if(e.target.templateInstance.model.column){
				var clickedSortColumn = e.target.templateInstance.model.column.name;
				if(clickedSortColumn === this.sortColumn){
					//column already sorted, reverse sort
					this.sortDescending = !this.sortDescending;
				}else{
					this.sortColumn = clickedSortColumn;
				}
			}
		},
		rowSelect: function(e,p){
			if(this.rowSelection && e.target.templateInstance && e.target.nodeName !== 'INPUT'){
				var model = e.target.templateInstance.model;
				var row = model.row;
				if(model.record) row = model.record.row;
				this.toggleRowFromSelected(row);
			}
		},
		/**
		 *	Expression Filters
		 */
		sortByKey: function(array, key, desc, columns, selected, pageSize, page, editRow, l) {
			//ignore l, it is used to trigger observe.js watch only
			var sortedArray = array.sort(function(a, b) {
				var x, y;
				//determine if computed field
				var formula = null;
				for(var i=0;i<columns.length;i++){
					if(columns[i].name === key){
						formula = columns[i].formula;
						break;
					}
				}
				if(formula){
					x = formula(a);
					y = formula(b);
				}else{
					x = a[key];
					y = b[key];
				}

				if(typeof x === 'undefined' || typeof y === 'undefined'){
					//sort undefined after
					if(typeof x === 'undefined'){
						return !desc;
					}else{
						return desc;
					}
				}else{
					if (typeof x === "string" && typeof y === "string" && !(isNaN(x) && isNaN(y))){
						x = x.toLowerCase();
						y = y.toLowerCase();
					}
					if(desc){
						return ((x < y) ? 1 : ((x > y) ? -1 : 0));
					}else{
						return ((x < y) ? -1 : ((x > y) ? 1 : 0));
					}
				}
			});
			var records = [];
			var isMultiSelect = this.isArray(selected);

			if(page < 1) page = 1;

			var start = (pageSize < Number.MAX_VALUE) ? Math.min(pageSize * (page - 1), sortedArray.length) : 0;
			var end = (pageSize < Number.MAX_VALUE) ? Math.min(start + +pageSize, sortedArray.length) : sortedArray.length;

			for(var i=start;i<end;i++){
				var row = sortedArray[i];
				var isSelected = isMultiSelect ? selected.indexOf(row) > -1 : row === selected;
				var isEditMode = editRow === row
				var fields = {};
				columns.forEach(function(column, index, array) {
					var value;
					if(column.formula){
						value = column.formula(row);
					}else{
						value = row[column.name];
					}
					fields[column.name] = { value: value, row: row, column: column, sortColumn: key };
				});
				records.push({ selected: isSelected, edit: isEditMode, fields: fields, row: row });
			}
			return records;
		},
		selectProperty: function(objects, column) {
			var arr = [];
			var rowArr = [];
			for(var i=0;i<objects.length;i++){
				var row = objects[i];
				var value;
				if(column.formula){
					value = column.formula(row);
				}else{
					value = row[column.name];
				}
				rowArr.push(row[column.name]);
				arr.push(value);
			}
			return { values: arr, rowValues: rowArr, column: column };
		},
		naturalNumbers: function(n,mid){
			if(mid !== null){
				var range = 4;
				var start = Math.max(1, mid-range);
				var end = Math.min(n, mid+range);
			}else{
				var start = 1;
				var end = n;
			}
			var arr = [];
			for(var i=start;i<=end;i++) arr.push(i);
			return arr;
		},
		/**
		  *	TODO: clean this up, it was put in to trigger a reprocess on initial load when templates are imported into the shadow dom.
		  */
		forceRefresh: 0,
		forceFooterRefresh: 1,
		refreshFooter: function(){ this.forceFooterRefresh++; }
	});
</script>
</polymer-element>
